extends Node2D
# 本脚本继承自 Node2D，适用于 2D 游戏场景

# 定义游戏状态枚举，便于管理游戏的不同阶段
enum GameState { READY, RUNNING, ENDED }

# 可在编辑器中调整的游戏参数：
@export var max_reaction_time := 1  # 最大反应时间（秒）
@export var target_probability := 0.2  # 目标（绿色）出现的概率（本例未在逻辑中直接使用，可用于后续扩展）

# 通过 onready 获取场景中各节点引用，用于显示颜色块、计时、UI显示等：
#@onready var stimulus: ColorRect = $Stimulus      # 刺激（颜色块）节点
@onready var timer: Timer = $Timer                # 定时器节点（未在代码中详细使用，可用于扩展）
@onready var score_label: Label = $CanvasLayer/Score    # 显示分数的标签
@onready var countdown_label: Label = $CanvasLayer/ScoreLabel/CountdownLabel  # 显示试次进度的标签
@onready var avg_reaction_time_label: Label = $CanvasLayer/ScoreLabel/AvgReactionTimeLabel  # 显示平均反应时间
@onready var reaction_time_variance_label: Label = $CanvasLayer/ScoreLabel/ReactionTimeVarianceLabel  # 显示反应时间方差
@onready var stimulus: Sprite2D = $Stimulus  # 改为Polygon2D节点

@onready var target_image_label: Label = $target_image_label  # 用于显示目标图片的标签


# 定义全局变量，保存游戏状态和统计数据：
var game_state: GameState = GameState.READY  # 初始状态为 READY
var current_trial := -1        # 当前试次计数器
var total_trials := 200        # 总试次数（本示例设置为25，可根据需要调整）
var score := 0                # 玩家得分
var reaction_times: Array[float] = []   # 记录玩家正确响应的反应时间（用于显示平均值和方差）
var is_target := false        # 标识当前试次是否为目标试次（绿色），在生成目标列表时使用
var trial_start_time := 0.0   # 记录当前试次开始时的时间（毫秒）
var target_list: Array[bool] = []  # 预生成的试次类型列表，保证一定数量的目标试次（true表示目标）
var responded := false        # 标识玩家是否已经在本次试次中响应
var display_timer: SceneTreeTimer   # 用于控制刺激显示时间的计时器
var interval_timer: SceneTreeTimer  # 用于试次间隔的计时器
#***************图片索引部分******************
var target_index = 0  # 从图片数组中随机选择目标图片
var target_image
var distractor_images = []



# ----- ACS 统计相关变量 -----
# 用于每一组试次（block）统计数据，后续计算 ACS 分数
var block_size := 162                      # 每组试次数量，用于 ACS 计算
var current_block_trial := 0              # 当前组内的试次计数
var block_reaction_times : Array[float] = []  # 当前组内记录目标试次的反应时间
var block_target_hits := 0                # 当前组内目标试次中玩家正确点击的次数
var block_omissions := 0                  # 当前组内目标试次中未响应（遗漏）的次数
var block_commissions := 0                # 当前组内干扰试次中误点击的次数
var all_acs_scores: Array[float] = []

var images = [
	preload("res://蘑菇/蘑菇1.png"),  # 替换为实际图片路径
	preload("res://蘑菇/蘑菇5.png"),  # 替换为实际图片路径
	preload("res://蘑菇/蘑菇6.png"),  # 替换为实际图片路径
	preload("res://蘑菇/蘑菇7.png")   # 替换为实际图片路径
]

func _process(delta: float):
	# 每一帧都更新颜色块的位置，确保居中
	#stimulus.position = (Vector2(get_viewport().size) - stimulus.size) / 2
	if current_trial % 5 == 0:
		$Control/Path2D/PathFollow2D/蝴蝶.visible = true
	elif current_trial % 5 == 3:
		$Control/Path2D/PathFollow2D/蝴蝶.visible = false
	
	# 更新按钮位置 - 屏幕中间的下2/3处
	if has_node("CanvasLayer/UI/StartButton"):  # 假设你的按钮节点路径是 UI/StartButton
		var button = $CanvasLayer/UI/StartButton
		var viewport_size = get_viewport().size
		# 计算位置：水平居中，垂直方向在2/3处（减去按钮高度的一半使其真正居中）
		button.position = Vector2(
		(viewport_size.x - button.size.x) / 2,
		(viewport_size.y * 3 / 4) - (button.size.y / 2)
		)
	
	# 更新标签位置 - 屏幕右1/4上1/4处
	if has_node("CanvasLayer/ScoreLabel"):  # 假设你的标签节点路径是 UI/MyLabel
		var label = $CanvasLayer/ScoreLabel
		var viewport_size = get_viewport().size
		# 计算位置：水平方向在3/4处（右1/4），垂直方向在1/4处
		label.position = Vector2(
			viewport_size.x * 2 / 3 - label.size.x / 2,
			viewport_size.y * 1/ 2 
		)
	
	

# 定义一个函数用于计算数组方差（用于计算反应时间方差）
func variance(arr: Array) -> float:
	if arr.size() < 2:
		return 0.0
	# 计算数组的平均值
	var mean = arr.reduce(func(a, b): return a + b) / arr.size()
	var sum_sq_diff = 0.0
	# 累加每个数据与均值的平方差
	for v in arr:
		sum_sq_diff += (v - mean) * (v - mean)
	return sum_sq_diff / arr.size()

# _ready() 在节点准备完毕时调用
func _ready():
	await get_tree().process_frame	# 等待一帧，确保 UI 组件已初始化；process_frame 是信号，无需括号
	randomize()	# 初始化随机种子
	$CanvasLayer/TargetMushroomBox.visible = false
	$CanvasLayer/StartButton.visible = true
	stimulus.visible = false
	$CanvasLayer/game_over.visible = false
	$CanvasLayer/ACS.visible = false
	$CanvasLayer/Ave_ACS.visible = false
	$CanvasLayer/暂停菜单.visible = false
	$CanvasLayer/pause.visible = false
	$Control/Path2D/PathFollow2D/蝴蝶.visible = false
	$CanvasLayer/规则面板/播放.play("规则弹出")
	$CanvasLayer/Score.visible=false
	#stimulus.color = NEUTRAL_COLOR	# 初始时将颜色块设置为中性颜色（灰色）
	update_ui()	# 更新 UI 显示（分数、试次等）



# 开始游戏，重置各项统计数据，并生成试次目标列表
func start_game():
	#stimulus.position = (Vector2(get_viewport().size) - stimulus.size) / 2  # 将刺激居中显示
	$CanvasLayer/StartButton.visible = false
	stimulus.visible = true
	$CanvasLayer/TargetMushroomBox.visible = true
	
	target_index = randi() % images.size()#确定哪个图片为目标点击
	target_image = images[target_index]
	$CanvasLayer/TargetMushroomBox/TargetImageRect.texture=target_image
	# 干扰图是除了目标图之外的图
	distractor_images = []
	for i in images.size():
		if i != target_index:
			distractor_images.append(images[i])
	
	
	await get_tree().create_timer(1.0).timeout
	game_state = GameState.RUNNING
	score = 0
	current_trial = 0
	reaction_times.clear()	# 清空总体反应时间记录
	# 同时重置 ACS 统计相关变量
	current_block_trial = 0
	block_reaction_times.clear()
	block_target_hits = 0
	block_omissions = 0
	block_commissions = 0
	generate_target_list()	# 生成目标试次列表
	start_random_color_phase()  # 开始第一轮试次

# 生成目标列表，此处示例生成 5 个目标试次和 20 个干扰试次，共 25 次
func generate_target_list():
	target_list.clear()
	for i in range(10):
		target_list.append(true)   # true 表示目标试次
	for i in range(40):
		target_list.append(false)  # false 表示干扰试次
	target_list.shuffle()          # 随机打乱顺序，确保试次顺序随机
	#print([target_list[-1]] + target_list.slice(0, target_list.size() - 1))
	print(target_list)
	
# 开始当前试次
func start_random_color_phase():
	# 如果所有试次完成，则结束游戏
	if current_trial >= total_trials:
		#$UI/StartButton.disabled = true
		end_game()
		return
	
	responded = false  # 重置本试次响应标志
	
	# 延迟后显示刺激
	await get_tree().create_timer(0.2).timeout  # 0.5秒延迟
	
	var is_target = target_list[current_block_trial]
	stimulus.modulate.a = 1.0
	if is_target:
		
		stimulus.texture = target_image
		
	else:
		stimulus.texture = distractor_images[randi() % distractor_images.size()]
	#使得stimulus.texture指向distractor_images数组元素中随机的一个
	
	# 启动一个计时器，随机显示1到2秒后超时
	display_timer = get_tree().create_timer(1.0)
	display_timer.timeout.connect(_on_display_timeout)
	
	update_ui()  # 更新 UI（例如记录试次开始时间）

# 当显示计时器超时时调用
func _on_display_timeout():
	# 超时后隐藏刺激，设置为中性颜色
	stimulus.modulate.a = 0.0
	# 如果本试次为目标且玩家未响应，则记录遗漏（未点击）
	if not responded:
		var is_target = target_list[current_block_trial]  # 用当前试次索引
		if is_target:
			score = max(0, score - 20)
			record_trial(true, false, 0.0)
		else:
			# 干扰试次正确忽略，不扣分
			record_trial(false, false, 0.0)

		update_ui()  # 记得更新分数显示
	
	# 试次结束后，等待1秒后开始下一试次
	var random_interval = randf_range(0.1, 0.5)
	interval_timer = get_tree().create_timer(random_interval)
	interval_timer.timeout.connect(start_random_color_phase)
	current_trial += 1  # 仅递增当前试次计数，由 record_trial() 负责更新 current_block_trial

# 处理玩家输入（响应）
func _input(event):
	# 只有在游戏运行且本试次未响应时才处理输入
	if game_state != GameState.RUNNING or responded:
		return
		
	var rt = (Time.get_ticks_msec() - trial_start_time) / 1000.0
	
	if event.is_action_pressed("respond"):
		responded = true  # 标记玩家已响应

		# 计算反应时间（秒），从试次开始时间到当前时间
		if  target_list[current_block_trial]:  # 上一轮是目标试次
			print(rt)
		# 如果当前试次为目标试次，则处理正确点击

			if rt < max_reaction_time:
				score += 100
				reaction_times.append(rt)
			record_trial(true, true, rt)  # 记录本次目标试次数据
		else: # 干扰试次
			# 干扰试次错误响应扣分
			score = max(0, score - 50)
			record_trial(false, true, 0.0)
		# 立即隐藏刺激
		stimulus.modulate.a = 0.0
		update_ui()

# 记录单次试验数据到 ACS 统计部分
func record_trial(is_target: bool, reacted: bool, reaction_time: float):
	current_block_trial += 1  # 组内试次计数加1
	if is_target:
		if reacted:
			block_target_hits += 1   # 正确响应目标试次计数
			block_reaction_times.append(reaction_time)  # 记录反应时间
		else:
			block_omissions += 1  # 未响应目标试次计数
	else:
		if reacted:
			block_commissions += 1  # 误点击干扰试次计数
	
	# 如果本组试次数达到 block_size，则计算并显示 ACS 分数，然后重置组内数据
	if current_block_trial >= block_size:
		calculate_and_display_ACS()
		generate_target_list()
		current_block_trial = 0
		block_reaction_times.clear()
		block_target_hits = 0
		block_omissions = 0
		block_commissions = 0

# 处理试次结果，主要用于将刺激恢复为中性状态并更新UI
#func process_trial_result():
	#stimulus.color = NEUTRAL_COLOR
	#update_ui()

# 游戏结束，显示最终结果，并重置部分数据
func end_game():
	game_state = GameState.ENDED
	$CanvasLayer/ACS.visible = true
	$CanvasLayer/Ave_ACS.visible = true
	$CanvasLayer/game_over.visible = true
	$CanvasLayer/pause.visible = true
	$Control/Path2D/PathFollow2D/蝴蝶.visible = false
	$CanvasLayer/pause.visible = false
	$CanvasLayer/StartButton.text ="重新开始"

	
	#$UI/StartButton.disabled = true
	show_results()
	
	# 1) 显示 “Game Over”
	$CanvasLayer/game_over.text = "Game Over"
	
	# 2) 显示所有的 ACS 分数到 label `ACS`
	var all_acs_text = "ACS Scores:\n"
	for score_value in all_acs_scores:
		all_acs_text += str(score_value) + "\n"
	$CanvasLayer/ACS.text = all_acs_text
	
	# 3) 计算并显示 ACS 平均值到 label `Ave ACS`
	if all_acs_scores.size() > 0:
		var sum_acs = 0.0
		for score_value in all_acs_scores:
			sum_acs += score_value
		var ave_acs = sum_acs / all_acs_scores.size()
		$CanvasLayer/"Ave_ACS".text = "Average ACS: %.2f" % ave_acs
	else:
		$CanvasLayer/"Ave_ACS".text = "No ACS data."
	
	# 下面这几行如果你希望在游戏结束后“清空”某些数据，可以保留；若要一直显示最终分数，可去掉
	score = 0
	current_trial = -1
	reaction_times.clear()
	update_ui()
	
	# 重新显示并启用开始按钮
	$CanvasLayer/StartButton.visible = true
	$CanvasLayer/StartButton.disabled = false
	$CanvasLayer/TargetMushroomBox.visible = false


# 更新 UI 信息，包括分数、试次、平均反应时间和反应时间方差
func update_ui():
	# 记录当前时间作为试次开始时间
	trial_start_time = Time.get_ticks_msec()
	score_label.text = "蘑菇分数: %d" % score
	countdown_label.text = "Trials: %d/%d" % [current_trial+1, total_trials]
	var avg_rt = 0.0
	var rt_var = 0.0
	if reaction_times.size() > 0:
		avg_rt = reaction_times.reduce(func(a, b): return a + b) / reaction_times.size()
		rt_var = variance(reaction_times)
	avg_reaction_time_label.text = "Avg Reaction Time: %.2f s" % avg_rt
	reaction_time_variance_label.text = "RT Variance: %.4f" % rt_var

# 显示游戏最终结果，在控制台打印
func show_results():
	var avg_rt = 0.0
	if reaction_times.size() > 0:
		avg_rt = reaction_times.reduce(func(a, b): return a + b) / reaction_times.size()
	var result_text = """Final Score: %d
	Average Reaction Time: %.2f ms
	Correct Responses: %d/%d
	""" % [score, avg_rt * 1000, reaction_times.size(), 10]
	#print(result_text)

# ----- ACS 分数计算部分 -----
# 计算并显示当前组内 ACS 分数和相关指标
func calculate_and_display_ACS():
	var avg_rt = 0.0
	var rt_std = 0.0
	if block_reaction_times.size() > 0:
		avg_rt = block_reaction_times.reduce(func(a, b): return a + b) / block_reaction_times.size()
		rt_std = calculate_std(block_reaction_times, avg_rt)
	
	# 计算命中率：假设组内目标试次数固定为 5（根据生成目标列表，此处示例为 5）
	var hit_rate = block_target_hits / 10.0
	# 计算误报率：组内干扰试次数 = block_size - 目标试次数；此示例中 block_size 为 50，则干扰数为 45
	var false_alarm_rate = block_commissions / 40.0
	
	# 将概率转换为 Z 分数，用于计算 d'
	var z_hit = get_z_score(hit_rate)
	var z_false_alarm = get_z_score(false_alarm_rate)
	# d' 为误报 Z 分数与命中 Z 分数之差
	var d_prime =  z_hit -z_false_alarm
	
	# 计算反应时间和变异性（RTV）的 Z 分数（需要根据常模数据调整均值和标准差）
	var rt_z = get_rt_z(avg_rt)
	var var_z = get_var_z(rt_std)
	
	# 最终 ACS 分数公式：ACS = RT Z + d' + RTV Z + 1.80
	var acs = rt_z + d_prime + var_z + 1.80
	
	# ------ 在此处保存本 block 的 acs 分数 ------
	all_acs_scores.append(acs)
	
	print("Block ACS:", acs, 
		  "\nAvg RT:", avg_rt, 
		  "\nRT Variability:", rt_std, 
		  "\nOmissions:", block_omissions, 
		  "\nCommissions:", block_commissions)
	
# 计算标准差函数（与上面的 variance 函数类似，此处单独定义用于 ACS 计算）
func calculate_std(arr: Array, mean: float) -> float:
	var sum_sq_diff = 0.0
	for rt in arr:
		sum_sq_diff += (rt - mean) * (rt - mean)
	return sqrt(sum_sq_diff / arr.size())

# 将概率转换为 Z 分数，内部调用逆正态分布函数
func get_z_score(rate: float) -> float:
	rate = clamp(rate, 0.00001, 0.99999)
	return get_tova_z(rate)

# 计算反应时间 Z 分数： (rt - 均值) / 标准差
func get_rt_z(rt: float) -> float:
	var mean_rt = 0.5349  # 示例均值（秒），根据常模数据调整
	var std_rt = 0.0843  # 示例标准差
	return (rt - mean_rt) / std_rt

# 计算反应时间变异性 Z 分数： (rt_std - 均值) / 标准差
func get_var_z(rt_std: float) -> float:
	var mean_std = 0.0791  # 示例均值
	var std_std = 0.0190  # 示例标准差
	return (rt_std - mean_std) / std_std

# 逆正态分布函数（采用 Abramowitz & Stegun 公式的近似实现）
func get_tova_z(rate: float) -> float:
	rate = clamp(rate, 0.00001, 0.99999)
	var p = 1.0 - rate
	var T = sqrt(log(1.0 / (p * p)))
	var c0 = 2.515517
	var c1 = 0.802853
	var c2 = 0.010328
	var d1 = 1.432788
	var d2 = 0.189269
	var d3 = 0.001308
	var numerator = c0 + c1 * T + c2 * T * T
	var denominator = 1.0 + d1 * T + d2 * T * T + d3 * T * T * T
	return T - numerator / denominator


# 处理开始按钮按下事件
func _on_start_button_pressed():
	$CanvasLayer/ACS.visible = false
	$CanvasLayer/Ave_ACS.visible = false
	$CanvasLayer/game_over.visible = false
	$CanvasLayer/pause.visible = true
	$CanvasLayer/规则面板/播放.play("规则收纳")
	$CanvasLayer/Score.visible=true
	start_game()
